﻿namespace EPiServer
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Core;
    using PageTypeBuilder;
    using PageTypeBuilder.Reflection;

    public interface IPropertyGroupComparer
    {
        bool IsNull();
    }

    public static class PropertyGroupExtensions
    {

        private const string TypedPageDataPropertyName = "TypedPageData";
        private const string HierarchyPropertyName = "Hierarchy";
        private const string HierarchyValuePropertyName = "Value";
        private const string PageTypePropertyGroupHierarchyTypeName = "PageTypeBuilder.PageTypePropertyGroupHierarchy";
        private static Type _propertyGroupHeirarchyType;

        public static TPropertyGroup GetPropertyGroupDynamicProperty<TPageData, TPropertyGroup>(this TPageData pageData,
            Expression<Func<TPageData, object>> expression,
            bool allowNullReturnValue = true)
            where TPropertyGroup : PageTypePropertyGroup, IPropertyGroupComparer, new()
            where TPageData : TypedPageData
        {
            string memberName = GetMemberExpression(expression).Member.Name;
            TPropertyGroup value = CreateAndPopulatePropertyGroupInstance<TPropertyGroup>(pageData, memberName);
            bool isNull = value.IsNull();

            if (isNull)
                value = null;

            if (isNull && pageData.ParentLink != null && pageData.PageLink != PageReference.StartPage)
            {
                PageData page = DataFactory.Instance.GetPage(pageData.ParentLink);

                if (page is TPageData)
                {
                    TPageData pageInstance = page as TPageData;
                    PropertyInfo propertyInfo = pageInstance.GetType().GetProperty(memberName);
                    value = propertyInfo.GetValue(pageInstance, null) as TPropertyGroup;

                    if (value != null)
                        SetPropertyValue(value, TypedPageDataPropertyName, pageData, BindingFlags.Instance | BindingFlags.NonPublic);
                }
            }

            if (value == null && !allowNullReturnValue)
                value = new TPropertyGroup();

            return value;
        }

        private static TPropertyGroup CreateAndPopulatePropertyGroupInstance<TPropertyGroup>(TypedPageData pageData, string memberName)
            where TPropertyGroup : PageTypePropertyGroup, new()
        {
            TPropertyGroup value = new TPropertyGroup();
            PropertyInfo[] properties = typeof(TPropertyGroup).GetPublicOrPrivateProperties();

            foreach (PropertyInfo property in properties.Where(current => current.GetCustomAttributes<PageTypePropertyAttribute>().Any()))
            {
                string propertyName = string.Format("{0}-{1}", memberName, property.Name);
                object propertyValue = pageData[propertyName];

                if (propertyValue == null && property.PropertyType == typeof(bool))
                    propertyValue = false;

                property.SetValue(value, propertyValue, null);
            }

            PropertyInfo typedPageDataProperty = properties.FirstOrDefault(p => string.Equals(p.Name, TypedPageDataPropertyName));

            if (typedPageDataProperty != null)
                typedPageDataProperty.SetValue(value, pageData, null);

            PropertyInfo hierarchyProperty = properties.FirstOrDefault(p => string.Equals(p.Name, HierarchyPropertyName));

            if (hierarchyProperty != null)
            {
                Type type = _propertyGroupHeirarchyType;

                if (type == null)
                {
                    type = Type.GetType(PageTypePropertyGroupHierarchyTypeName);
                    _propertyGroupHeirarchyType = type;
                }

                if (type != null)
                {
                    object instance = Activator.CreateInstance(type);
                    SetPropertyValue(instance, HierarchyValuePropertyName, memberName, BindingFlags.Instance | BindingFlags.Public); 
                    hierarchyProperty.SetValue(value, instance, null);
                }
            }

            return value;
        }

        private static MemberExpression GetMemberExpression<TPageTypePropertyGroup, TProperty>(Expression<Func<TPageTypePropertyGroup, TProperty>> expression)
        {
            MemberExpression memberExpression = null;

            if (expression.Body is MemberExpression)
            {
                memberExpression = (MemberExpression)expression.Body;
            }
            else if (expression.Body is UnaryExpression)
            {
                UnaryExpression unaryExpression = (UnaryExpression)expression.Body;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }

            if (memberExpression == null)
                throw new Exception("The body of the expression must be either a MemberExpression of a UnaryExpression.");

            return memberExpression;
        }

        private static void SetPropertyValue(object obj, string propertyName, object value, BindingFlags bindingFlags)
        {
            PropertyInfo property = obj.GetType().GetProperty(propertyName, bindingFlags);

            if (property != null)
                property.SetValue(obj, value, null);
        }
    }
}